package main

import (
	"fmt"
	"log"
	"os"
	"strings"

	"apiote.xyz/p/eeze/secrets"

	"git.sr.ht/~sircmpwn/getopt"
	"git.sr.ht/~sircmpwn/go-bare"
)

type Fields struct {
	fields *[]string
}

func (f Fields) String() string {
	return strings.Join(*f.fields, ",")
}
func (f Fields) Set(s string) error {
	fields := strings.Split(s, ",")
	*f.fields = fields
	return nil
}

func fullUsage() {
	fmt.Fprintln(getopt.CommandLine.Output(), "Usage of eeze:")
	fmt.Fprintln(getopt.CommandLine.Output(), `
  eeze -A
    add secret(s);

    reads a
    []map[string]struct{
      Hidden bool
      Value string
    }
    from stdin as dirty
  eeze -D -i <id>
    delete a secret by <id>
  eeze -E
    export all secrets as dirty
  eeze -G [-e|-p] [-f <fields>] (-i <id>|-l <label> [-u <username>]|-s <url> [-u <username>])
    get secrets

    -e             export as dirty
    -p             pretty print
    -f <fields>    get only fields for secrets; fields are comma separated
    -i <id>        get secret by id field
    -l <label>     get secret by label field
    -s <url>       get secret by url field
    -u <username>  filter secrets by username field
  eeze -L
    list all secrets in format "id|label" or "id|label: username"
  eeze -Ik
    list credentials, name and type
  eeze -Ir
    recreate secrets index
  eeze -Ia -n <name> (-p|-2)
    add credential with <name>

    -p             password credential
    -2             FIDO2 credential
  eeze -Id -n <name>
    delete credential named <name>
  eeze -h
    show this help and exit`)
	os.Exit(0)
}

func shortUsage() {
	fmt.Fprintln(getopt.CommandLine.Output(), "Usage of eeze:")
	fmt.Fprintln(getopt.CommandLine.Output(), `
  eeze -A
  eeze -D -i <id>
  eeze -E
  eeze -G [-e|-p] [-f <fields>] (-i <id>|-l <label> [-u <username>]|-s <url> [-u <username>])
  eeze -L
  eeze -Ik
  eeze -Ir
  eeze -Ia -n <name> (-p|-2)
  eeze -Id -n <name>
  eeze -h`)
	os.Exit(0)
}

func main() {
	bare.RegisterUnion((*secrets.Credential)(nil)).
		Member(*new(secrets.FidoCredential), 0).
		Member(*new(secrets.PasswordCredential), 1)

	I := getopt.Bool("I", false, "Init")
	A := getopt.Bool("A", false, "Add")
	G := getopt.Bool("G", false, "Get")
	E := getopt.Bool("E", false, "Export")
	L := getopt.Bool("L", false, "List")
	D := getopt.Bool("D", false, "Delete")

	addCred := getopt.Bool("a", false, "in I, add credential")
	delCred := getopt.Bool("d", false, "in I, delete credential")
	listCreds := getopt.Bool("k", false, "in I, list credentials")
	credName := getopt.String("n", "", "in I, credential name")
	recreateIndex := getopt.Bool("r", false, "in I, recreate index")
	fido2 := getopt.Bool("2", false, "in I, add FIDO2 credential")
	p := getopt.Bool("p", false, "in I, add password; in G, add headers")
	idGet := getopt.String("i", "", "in G and D, secret ID")
	labelGet := getopt.String("l", "", "in G, secret label")
	urlGet := getopt.String("s", "", "in G, secret URL")
	usernameGet := getopt.String("u", "", "in G, secret username")
	fields := &[]string{}
	getopt.Var(&Fields{fields}, "f", "in G, comma separated fields to show")
	export := getopt.Bool("e", false, "in G, export as dirty")
	help := getopt.Bool("h", false, "show help")

	err := getopt.Parse()
	if err != nil {
		log.Println("while parsing opts", err)
		os.Exit(1)
	}

	if *help {
		getopt.SetOutput(os.Stdout)
		fullUsage()
	}

	err = secrets.InitialiseCredentials()
	if err != nil {
		log.Println("while initialising", err)
		os.Exit(1)
	}

	if !(*I || *A || *G || *E || *L || *D) {
		shortUsage()
	}

	key, err := secrets.Open()
	if err != nil {
		log.Println("while opening credentials:", err)
		os.Exit(1)
	}

	switch {
	case *E:
		entries, err := secrets.List(key)
		if err != nil {
			log.Println("while listing secrets", err)
			os.Exit(1)
		}
		ids := []string{}
		for _, entry := range entries {
			ids = append(ids, entry.ID)
		}
		secretsArr, err := secrets.Get(ids, key)
		if err != nil {
			log.Println("while getting secrets:", err)
			os.Exit(1)
		}
		fmt.Print(secrets.Format(secretsArr, []string{}, true, false))
	case *I:
		switch {
		case *addCred:
			if *fido2 && *p {
				log.Println("Both FIDO2 and password requested")
				os.Exit(1)
			}
			err := secrets.AddCredential(*fido2, *p, *credName)
			if err != nil {
				log.Println("while adding credential", err)
				os.Exit(1)
			}
		case *recreateIndex:
			err := secrets.RecreateIndex(key)
			if err != nil {
				log.Println("while recreating index", err)
				os.Exit(1)
			}
		case *delCred:
			err := secrets.DelCredential(*credName)
			if err != nil {
				log.Println("while deleting credential", err)
				os.Exit(1)
			}
		case *listCreds:
			err := secrets.ListCredentials()
			if err != nil {
				log.Println("while listing credentials", err)
				os.Exit(1)
			}
		default:
			log.Println("No action given")
			os.Exit(1)
		}
	case *A:
		err := secrets.Add(key)
		if err != nil {
			log.Println("while adding secrets", err)
			os.Exit(1)
		}
	case *D:
		err := secrets.Delete(*idGet, key)
		if err != nil {
			log.Println("while deleting secrets", err)
			os.Exit(1)
		}
	case *G:
		if *idGet == "" && *labelGet == "" && *urlGet == "" {
			log.Println("none of ID, label, URL given")
			os.Exit(1)
		}
		ids, err := secrets.Filter(*idGet, *labelGet, *urlGet, *usernameGet, key)
		if err != nil {
			log.Println("while filtering secret IDs:", err)
			os.Exit(1)
		}
		secretsArr, err := secrets.Get(ids, key)
		if err != nil {
			log.Println("while getting secrets:", err)
			os.Exit(1)
		}
		fmt.Print(secrets.Format(secretsArr, *fields, *export, *p))
	case *L:
		entries, err := secrets.List(key)
		if err != nil {
			log.Println("while listing secrets", err)
			os.Exit(1)
		}
		for _, entry := range entries {
			fmt.Printf("%s|%s", entry.ID, entry.Label)
			if entry.Username != "" {
				fmt.Printf(": %s", entry.Username)
			}
			fmt.Println("")
		}
	default:
		shortUsage()
	}
}
